# This file is part of trytond-pcw-purchase module for Tryton. 
# The COPYRIGHT file at the top level of this repository contains 
# the full copyright notices and license terms.

from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.wizard import Wizard, StateAction, StateView, StateTransition, Button
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval, If, Bool, In
from trytond.transaction import Transaction
from decimal import Decimal

__all__ = ['Purchase', 'PurchaseLine']
__metaclass__ = PoolMeta

class Purchase(Workflow, ModelSQL, ModelView):
    "Pcw Purchase"
    __name__ = 'pcw.purchase'

    name = fields.Char('Purchase', size=None, readonly=True, select=True)
    party = fields.Many2One('party.party', 'Party', required=True, select=True,
        states={
            'readonly': Eval('state') != 'draft',
            },
        depends=['state'])
    date = fields.Date('Date',
        states={
            'readonly': Eval('state').in_(['done', 'cancel']),
            'required': Eval('state').in_(['draft', 'approved']),
            },select=True)
    reference = fields.Char('Reference',
        states={
            'readonly': Eval('state') != 'draft',
            },
        depends=['state'], select=True)
    state = fields.Selection([
        ('draft', 'Draft'),
        ('approved', 'Approved'),
        ('done', 'Done'),
        ('cancel', 'Cancelled'),
        ], 'Status', readonly=True, required=True)
    purchase_lines = fields.One2Many('pcw.purchase.line', 'purchase', 'Details', 
        states={
            'readonly': Eval('state')  != 'draft',
            }, 
        on_change=['purchase_lines', 'state', 'currency','total_lines','total_tax','total_global_discount'],
        depends=['party', 'state'])
    notes = fields.Text('Notes')
    company = fields.Many2One('company.company', 'Company', required=True,
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=', '!='),
                Eval('context', {}).get('company', 0)),
            ],
        select=True, readonly=True)
    currency = fields.Many2One('currency.currency', 'Currency', required=True,
        states={
            'readonly': True,
            },
        depends=['state'])
    total_lines = fields.Function(fields.Numeric('Total lines',
            digits=(16, 4)), 'get_total_lines')
    total_lines_cache = fields.Numeric('Total lines',
        digits=(16, 4), readonly=True)
    total_tax = fields.Numeric('Tax',
        digits=(16, 4),
        states={
            'readonly': Eval('state') != 'draft',
            },
        on_change=['purchase_lines', 'state', 'currency','total_lines','total_tax','total_global_discount'])
    total_global_discount = fields.Numeric('Global Discount',
        digits=(16, 4),
        states={
            'readonly': Eval('state') != 'draft',
            },
        on_change=['purchase_lines', 'state', 'currency','total_lines','total_tax','total_global_discount'])
    total = fields.Function(fields.Numeric('Total',
            digits=(16, 4)), 'get_total_amount')

    @classmethod
    def __setup__(cls):
        super(Purchase, cls).__setup__()
        cls._order.insert(0, ('id', 'DESC'))
        cls._order.insert(1, ('name', 'DESC'))
        cls._order.insert(2, ('date', 'DESC'))
        cls._sql_constraints = [
            ('name_uniq', 'unique(name, company)', 'Purchase number must be uniq per company!'),
        ]

        cls._error_messages.update({
                'purchase_no_move': "Purchase has no details.",
                'delete_cancel': ('Purchase "%s" must be cancelled before '
                    'deletion.'),
                })

        cls._transitions |= set((
                ('draft', 'approved'),
                ('cancel', 'draft'),
                ('approved', 'done'),
                ('approved', 'cancel'),
                ('done', 'cancel'),
                ))

        cls._buttons.update({
                'cancel': {
                    'invisible': Eval('state').in_(['cancel', 'draft']),
                    },
                'draft': {
                    'invisible': ~Eval('state').in_(['cancel']),
                    'icon': If(Eval('state') == 'cancel', 'tryton-clear','tryton-go-previous'),
                    },
                'approve': {
                    'invisible': Eval('state') != 'draft',
                    'readonly': ~Eval('purchase_lines', []),
                    },
                'confirm': {
                    'invisible': Eval('state') != 'approved',
                    'readonly': ~Eval('purchase_lines', []),
                    },
                })

        # The states where amounts are cached
        cls._states_cached = ['approved', 'done', 'cancel']


    @staticmethod
    def default_state():
        return 'draft'

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_currency():
        Company = Pool().get('company.company')
        company = Transaction().context.get('company')
        if company:
            return Company(company).currency.id

    @staticmethod
    def default_total_tax():
        return Decimal(0.00)

    @staticmethod
    def default_total_global_discount():
        return Decimal(0.00)

    @classmethod
    def delete(cls, orders):
        for order in orders:
            if order.state in ['done','approved']:
                cls.raise_user_error('delete_cancel', error_args=(str(order.name),))                
        super(Purchase, cls).delete(orders)

    def on_change_total_tax(self):
        return self.on_change_purchase_lines()

    def on_change_total_global_discount(self):
        return self.on_change_purchase_lines()

    def on_change_purchase_lines(self):
        res = {}
        if not self.purchase_lines:
            return res
        res['total_lines'] = self.get_total_lines()
        res['total'] = self.get_total_amount()
        if not self.total_tax:
            res['total_tax'] = Decimal(0)
        if not self.total_global_discount:
            res['total_global_discount'] = Decimal(0)
        return res

    @classmethod
    def set_reference(cls, orders):
        '''
        Fill the name field with the purchase sequence
        '''
        pool = Pool()
        Sequence = pool.get('ir.sequence')
        Config = pool.get('pcw.purchase.configuration')

        config = Config(1)
        for order in orders:
            if order.name:
                continue
            name = Sequence.get_id(config.purchase_sequence.id)
            cls.write([order], {
                    'name': name,
                    })

    def get_total_amount(self, name=None):
        total_tax = self.total_tax
        if not total_tax:
            total_tax = Decimal(0)
        total_global_discount = self.total_global_discount
        if not total_global_discount:
            total_global_discount = Decimal(0)
        return self.currency.round(self.get_total_lines() + total_tax - total_global_discount)

    def get_total_lines(self, name=None):
        if (self.state in self._states_cached
                and self.total_lines_cache is not None):
            return self.total_lines_cache
        return self.get_line_total()

    def get_line_total(self):
        amount = Decimal(0)
        for line in self.purchase_lines:
            if line.amount:
                amount = amount + line.amount
        return self.currency.round(amount)

    def check_order(self):
        total = self.get_line_total()
        if total == 0:
            self.raise_user_error('purchase_no_move')

    @classmethod
    def store_cache(cls, orders):
        for order in orders:
            cls.write([order], {
                    'total_lines_cache': order.total_lines,
                    })

    def create_move(self, line):
        if line.quantity == 0 or line.product.type == 'service':
            return None

        Move = Pool().get('pcw.stock.move')

        mv = Move()
        mv.date = self.date
        mv.type = 'in'
        mv.reference = self.name
        mv.product = line.product
        mv.quantity = line.quantity
        mv.uom = line.unit
        mv.unit_price = line.unit_price
        mv.company = self.company
        mv.currency = self.company.currency
        mv.order = self
        mv.save()
        if mv.id:
            line.move = mv.id
            line.save()
            line.product.set_last_cost(line.unit_price, line.unit)
            if not line.product.provider_first:
                with Transaction().set_user(0) :
                    line.product.provider_first = self.party
                    line.product.save()
        return mv

    def create_moves(self):
        pool = Pool()
        Move = pool.get('pcw.stock.move')
        moves=[]

        for line in self.purchase_lines:
            mv = self.create_move(line)
            if mv:
                moves.append(mv)  

        if moves:
            Move.assign(moves)
            Move.confirm(moves)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancel')
    def cancel(cls, orders):
        Move = Pool().get('pcw.stock.move')
        cls.store_cache(orders)
        for order in orders:
            for line in order.purchase_lines:
                if line.move:
                    Move.cancel([line.move])
                    Move.delete([line.move])

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, orders):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('approved')
    def approve(cls, orders):
        for order in orders:
            order.check_order()
        cls.set_reference(orders)
        cls.store_cache(orders)

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    def confirm(cls, orders):
        for order in orders:
            order.check_order()
        cls.store_cache(orders)
        cls.process(orders)

    @classmethod
    def process(cls, orders):
        done = []
        for order in orders:
            if order.state in ('done', 'cancel'):
                continue
            order.create_moves()
            done.append(order)
        if done:
            cls.write(done, {
                    'state': 'done',
                    })


class PurchaseLine(ModelSQL, ModelView):
    'Pcw Purchase Line'
    __name__ = 'pcw.purchase.line'

    purchase = fields.Many2One('pcw.purchase', 'Purchase', ondelete='CASCADE', select=True)
    product = fields.Many2One('product.product', 'Product',
        domain=[
            ('purchasable', '=', True),
            ],
        states={
            'readonly': ~Eval('_parent_purchase'),
            }, 
        on_change=['product', 'quantity', 'unit_price'], required=True)
    description = fields.Text('Description', size=None)
    unit = fields.Many2One('product.uom', 'Unit',
            states={
                'required': Bool(Eval('product')),
            },
        domain=[
            If(Bool(Eval('product_uom_category')),
                ('category', '=', Eval('product_uom_category')),
                ('category', '!=', -1)),
            ],
        on_change=['product', 'quantity', 'unit', 'unit_price',
            '_parent_purchase.party'],
        depends=['product', 'product_uom_category'])
    quantity = fields.Float('Qty',
        digits=(16, 2),
        states={
            'required': Bool(Eval('product')),
            'readonly': ~Eval('_parent_purchase', {}),
            }, 
        on_change=['product', 'quantity', 'unit'],)
    unit_price = fields.Numeric('Price', digits=(16, 4),
        states={
            'required': Bool(Eval('product')),
            },
        on_change=['product', 'quantity', 'unit'],)
    product_uom_category = fields.Function(
        fields.Many2One('product.uom.category', 'Product Uom Category',
            on_change_with=['product']), 'on_change_with_product_uom_category')
    amount = fields.Function(fields.Numeric('Total',
            digits=(16, 4),
            states={
                'readonly': ~Eval('_parent_purchase'),
                }, 
        on_change_with=['product','quantity', 'unit_price', 'unit']), 'get_amount')
    move = fields.Many2One('pcw.stock.move', 'Move', readonly=True)


    def on_change_with_product_uom_category(self, name=None):
        if self.product:
            return self.product.default_uom_category.id

    def on_change_with_amount(self):
        amount = Decimal(self.quantity or 0.0) * (self.unit_price or Decimal(0.0))
        return amount

    def get_amount(self, name):
        return Decimal(self.quantity) * self.unit_price

    def on_change_product(self):
        res = {}
        res['quantity'] = 1.0
        res['unit_price'] = 0
        if not self.product:
            res['unit'] = None
            return res
        res['description'] = self.product.template.name        
        if self.product.purchasable and self.product.purchase_uom:
            res_uom = self.product.purchase_uom
        else:
            res_uom = self.product.default_uom
        res['unit'] = res_uom.id
        res['unit_price'] = self.product.get_last_cost(res_uom)
        return res

    def on_change_unit(self):
        res = {}
        if not self.unit:
            return res 
        res['unit_price'] = self.product.get_last_cost(self.unit)
        return res

    def on_change_quantity(self):
        res = {} 
        if not self.unit or not self.product:
            return res
        return res

    def on_change_unit_price(self):
        return self.on_change_quantity()

